/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 *
 */

// buildMode和buildOption能力

import { expect } from '@jest/globals';
import fs from 'fs';
import path from 'path';

import { BuildOpt } from '../../src/options/build/build-opt';

import { initByStageEmptyApp, initExecuteCommand, runHvigor, runShouldFail } from './common/common';
import { ModuleBuildProfileModifier } from './common/modifier/module-build-profile-modifier';
import { ProjectBuildProfileModifier } from './common/modifier/project-build-profile-modifier';
import { Probes } from './common/probes';

describe('build mode supports', function () {
  describe('project level', function () {
    const testPattern = (dir: string, product: string, command: string[], testBuildOpt: BuildOpt) => {
      const result = runHvigor(dir, command);
      expect(result.stdout).toContain('BUILD SUCCESSFUL');

      // 用正则匹配日志中打出的buildOption
      const regResult = RegExp(`Product '${product}' using build option:(.*?) in this build`, 's')
        .exec(result.stdout);
      expect(regResult).not.toBeNull();
      const logBuildOption = JSON.parse(regResult![1]);
      expect(logBuildOption).toEqual(testBuildOpt);
    };

    test.concurrent('should pick right product & buildMode build option', async () => {
      await initByStageEmptyApp({
        prepare: buildModeInitial,
        cb: async (dir) => {
          // buildMode预设值符合预期
          testPattern(dir, 'default', initExecuteCommand({
            mode: 'project',
            product: 'default',
            tasks: 'PreBuildApp',
          }), {
            debuggable: true,
            resOptions: {
              fromProduct: 'default'
            }
          });

          testPattern(dir, 'default', initExecuteCommand({
            mode: 'project',
            product: 'default',
            tasks: 'PreBuildApp',
            buildMode: 'test'
          }), {
            debuggable: true,
            resOptions: {
              fromProduct: 'default'
            }
          });

          // 设置product和build mode，合并符合预期
          testPattern(dir, 'custom', initExecuteCommand({
            mode: 'project',
            product: 'custom',
            tasks: 'PreBuildApp',
            buildMode: 'custom'
          }), {
            resOptions: {
              fromBuildMode: 'custom',
              fromProduct: 'custom'
            }
          });

          // assembleApp会使用release配置
          testPattern(dir, 'custom', initExecuteCommand({
            mode: 'project',
            product: 'custom',
            tasks: 'assembleApp',
          }), {
            debuggable: false,
            resOptions: {
              fromProduct: 'custom'
            }
          });

          // 命令行参数覆盖
          testPattern(dir, 'custom', initExecuteCommand({
            mode: 'project',
            product: 'custom',
            tasks: 'assembleApp',
            debuggable: 'true'
          }), {
            debuggable: true,
            resOptions: {
              fromProduct: 'custom'
            }
          });
        }
      });
    });

    test.concurrent('should overwrite default buildMode', async () => {
      await initByStageEmptyApp({
        prepare: buildModeInitial,
        cb: async (dir) => {
          const probes = new Probes(dir, path.resolve(dir, 'entry'));
          const outDir = path.resolve(probes.getIntermediatesLoaderOutPath(), 'ets');
          new ProjectBuildProfileModifier(dir).updateBuildMode({
            name: 'release',
            buildOption: {
              debuggable: true
            }
          }).outputSync();

          testPattern(dir, 'default', initExecuteCommand({
            mode: 'project',
            product: 'default',
            tasks: 'assembleApp',
            buildMode: 'release'
          }), {
            debuggable: true,
            resOptions: {
              fromProduct: 'default'
            }
          });
          expect(fs.readdirSync(outDir)).toContain('sourceMaps.map');
        }
      });
    });
  });

  describe('module level', function () {
    const testPattern = (dir: string, module: string, target: string, command: string[], testBuildOpt: BuildOpt) => {
      const result = runHvigor(dir, command);
      expect(result.stdout).toContain('BUILD SUCCESSFUL');

      // 用正则匹配日志中打出的buildOption
      const regResult = RegExp(`Module '${module}' target '${target}' using build option:(.*?) in this build`, 's')
        .exec(result.stdout);
      expect(regResult).not.toBeNull();
      const logBuildOption = JSON.parse(regResult![1]);
      expect(logBuildOption).toEqual(testBuildOpt);
      return result;
    };

    test.concurrent('should pick right target & buildMode build option', async () => {
      await initByStageEmptyApp({
        prepare: buildModeInitial,
        cb: async (dir) => {
          const probes = new Probes(dir, path.resolve(dir, 'entry'));
          const outDir = path.resolve(probes.getIntermediatesLoaderOutPath(), 'ets');
          new ModuleBuildProfileModifier(dir, 'entry')
            .addBuildModeBinder('custom', 'default', 'custom').outputSync();

          testPattern(dir, 'entry', 'default', initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
            buildMode: 'custom'
          }), {
            name: 'custom',
            resOptions: {
              fromProduct: 'default',
              fromBuildOption: 'custom',
              fromBuildMode: 'custom',
              fromTargetConfig: 'default',
              fromBaseBuildOption: 'base'
            }
          });
          expect(fs.readdirSync(outDir)).toContain('sourceMaps.map');
        }
      });
    });

    test.concurrent('should has right default build option set', async () => {
      await initByStageEmptyApp({
        prepare: buildModeInitial,
        cb: async (dir) => {
          const probes = new Probes(dir, path.resolve(dir, 'entry'));
          const outDir = path.resolve(probes.getIntermediatesLoaderOutPath(), 'ets');

          testPattern(dir, 'entry', 'default', initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
          }), {
            name: 'debug',
            copyFrom: 'default',
            debuggable: true,
            resOptions: {
              fromProduct: 'default',
              fromTargetConfig: 'default',
              fromBaseBuildOption: 'base'
            }
          });
          expect(fs.readdirSync(outDir)).toContain('sourceMaps.map');

          testPattern(dir, 'entry', 'default', initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
            buildMode: 'debug'
          }), {
            name: 'debug',
            copyFrom: 'default',
            debuggable: true,
            resOptions: {
              fromProduct: 'default',
              fromTargetConfig: 'default',
              fromBaseBuildOption: 'base'
            }
          });
          expect(fs.readdirSync(outDir)).toContain('sourceMaps.map');

          testPattern(dir, 'entry', 'default', initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
            buildMode: 'release'
          }), {
            name: 'release',
            copyFrom: 'default',
            debuggable: false,
            resOptions: {
              fromProduct: 'default',
              fromTargetConfig: 'default',
              fromBaseBuildOption: 'base'
            }
          });
          expect(fs.readdirSync(outDir)).not.toContain('sourceMaps.map');

          testPattern(dir, 'entry', 'default', initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
            buildMode: 'custom'
          }), {
            name: 'default',
            debuggable: true,
            resOptions: {
              fromProduct: 'default',
              fromBuildMode: 'custom',
              fromTargetConfig: 'default',
              fromBaseBuildOption: 'base'
            }
          });
          expect(fs.readdirSync(outDir)).toContain('sourceMaps.map');
        }
      });
    });

    test.concurrent('should enable command line input', async () => {
      await initByStageEmptyApp({
        prepare: buildModeInitial,
        cb: async (dir) => {
          new ModuleBuildProfileModifier(dir, 'entry')
            .addBuildModeBinder('custom', 'default', 'custom').outputSync();

          testPattern(dir, 'entry', 'default', initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
            buildMode: 'custom',
            debuggable: 'false'
          }), {
            name: 'custom',
            debuggable: false,
            resOptions: {
              fromProduct: 'default',
              fromBuildOption: 'custom',
              fromBuildMode: 'custom',
              fromTargetConfig: 'default',
              fromBaseBuildOption: 'base'
            }
          });

          const probes = new Probes(dir, path.resolve(dir, 'entry'));
          const outDir = path.resolve(probes.getIntermediatesLoaderOutPath(), 'ets');
          expect(fs.readdirSync(outDir)).not.toContain('sourceMaps.map');
        }
      });
    });

    test.concurrent('should not have invalid bind', async () => {
      await initByStageEmptyApp({
        prepare: buildModeInitial,
        cb: async (dir) => {
          // test invalid buildOptionName
          const moduleBuildProfileModifier = new ModuleBuildProfileModifier(dir, 'entry');
          moduleBuildProfileModifier.addBuildModeBinder('custom', 'default', 'empty').outputSync();

          const command = initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'default@PreBuild',
          });
          runShouldFail(dir, command, 'Build option \'empty\' used in buildModeBinder is not declared');

          // test invalid targetName
          moduleBuildProfileModifier.addBuildModeBinder('custom', 'empty', 'empty');
          runShouldFail(dir, command, 'Target \'empty\' used in buildModeBinder is not declared');

          // test invalid buildMode
          moduleBuildProfileModifier.addBuildModeBinder('empty', 'empty', 'empty');
          runShouldFail(dir, command, 'Build mode \'empty\' used in buildModeBinder is not declared');
        }
      });
    });

    test.concurrent('should overwrite default buildOption', async () => {
      await initByStageEmptyApp({
        prepare: buildModeInitial,
        cb: async (dir) => {
          const probes = new Probes(dir, path.resolve(dir, 'entry'));
          const outDir = path.resolve(probes.getIntermediatesLoaderOutPath(), 'ets');
          new ModuleBuildProfileModifier(dir, 'entry').setBuildOptionSet([
            {
              name: 'custom',
              resOptions: {
                fromBuildOption: 'custom'
              }
            },
            {
              name: 'release',
              debuggable: true,
              resOptions: {
                fromBuildOption: 'release'
              },
            }
          ]).outputSync();

          testPattern(dir, 'entry', 'default', initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
            buildMode: 'release'
          }), {
            copyFrom: 'default',
            name: 'release',
            debuggable: true,
            resOptions: {
              fromProduct: 'default',
              fromBuildOption: 'release',
              fromTargetConfig: 'default',
              fromBaseBuildOption: 'base'
            }
          });
          expect(fs.readdirSync(outDir)).toContain('sourceMaps.map');
        }
      });
    });

    test.concurrent('should overwrite default buildOption2', async () => {
      await initByStageEmptyApp({
        prepare: buildModeInitial,
        cb: async (dir) => {
          const probes = new Probes(dir, path.resolve(dir, 'entry'));
          const outDir = path.resolve(probes.getIntermediatesLoaderOutPath(), 'ets');
          new ModuleBuildProfileModifier(dir, 'entry').setBuildModeBinder([
            {
              buildModeName: 'debug',
              mappings: [
                {
                  targetName: 'default',
                  buildOptionName: 'release'
                }
              ]
            }
          ]).outputSync();

          testPattern(dir, 'entry', 'default', initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
            buildMode: 'debug'
          }), {
            copyFrom: 'default',
            name: 'release',
            debuggable: false,
            resOptions: {
              fromProduct: 'default',
              fromTargetConfig: 'default',
              fromBaseBuildOption: 'base'
            }
          });
          expect(fs.readdirSync(outDir)).not.toContain('sourceMaps.map');

          new ModuleBuildProfileModifier(dir, 'entry').setBuildModeBinder([
            {
              buildModeName: 'release',
              mappings: [
                {
                  targetName: 'default',
                  buildOptionName: 'debug'
                }
              ]
            }
          ]).outputSync();

          testPattern(dir, 'entry', 'default', initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
            buildMode: 'release'
          }), {
            copyFrom: 'default',
            name: 'debug',
            debuggable: true,
            resOptions: {
              fromProduct: 'default',
              fromTargetConfig: 'default',
              fromBaseBuildOption: 'base'
            }
          });
          expect(fs.readdirSync(outDir)).toContain('sourceMaps.map');
        }
      });
    });

    test.concurrent('copy from test', async () => {
      await initByStageEmptyApp({
        prepare: buildModeInitial,
        cb: async (dir) => {
          new ModuleBuildProfileModifier(dir, 'entry').setBuildOptionSet([{
            name: 'option',
            copyFrom: 'debug'
          }]).setBuildModeBinder([{
            buildModeName: 'debug',
            mappings: [{
              targetName: 'default',
              buildOptionName: 'option'
            }]
          }]).outputSync();

          const result = testPattern(dir, 'entry', 'default', initExecuteCommand({
            mode: 'module',
            product: 'default',
            module: 'entry@default',
            tasks: 'assembleHap',
            buildMode: 'debug'
          }), {
            copyFrom: 'debug',
            name: 'option',
            debuggable: true,
            resOptions: {
              fromProduct: 'default',
              fromTargetConfig: 'default',
              fromBaseBuildOption: 'base'
            }
          });
          expect(result.stderr).not.toContain('option copyFrom name:debug not exist');
        }
      });
    });
  });
});

export async function buildModeInitial(dir: string) {
  // 初始化工程级buildProfile
  new ProjectBuildProfileModifier(dir).updateProduct({
    name: 'default',
    buildOption: {
      resOptions: {
        fromProduct: 'default'
      }
    }
  }).addProduct({
    name: 'custom',
    buildOption: {
      resOptions: {
        fromProduct: 'custom'
      }
    }
  }).addBuildMode({
    name: 'custom',
    buildOption: {
      resOptions: {
        fromBuildMode: 'custom'
      }
    }
  }).updateModuleTargetByName({
    name: 'default',
    applyToProducts: [
      'default',
      'custom'
    ]
  }, 'entry').outputSync();

  // 初始化entry中模块级buildProfile
  new ModuleBuildProfileModifier(dir, 'entry').setBuildOption({
    resOptions: {
      fromBaseBuildOption: 'base'
    }
  }).setBuildOptionSet([
    {
      name: 'custom',
      resOptions: {
        fromBuildOption: 'custom'
      }
    }
  ]).updateTarget({
    name: 'default',
    config: {
      buildOption: {
        resOptions: {
          fromTargetConfig: 'default'
        }
      }
    }
  }).outputSync();
}
